import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;

class dvnode{
	
	public static void main(String[] args) throws SocketException, UnknownHostException{
	
		if(args.length < 3){
			System.out.println("At least 3 argumets is required. Program terminates.");
			System.exit(0);
		}
		try{
			selfPort = Integer.parseInt(args[0]);
			if(selfPort<1024){
				System.out.println("Self port must be greater than 1024. Program terminates.");
				System.exit(0);
			}
		}catch(Exception e){
			System.out.println("Self port must be an integer. Program terminates.");
			System.exit(0);
		}
		
		for(int i=1; i+1 < args.length; i=i+2){
			int port;
			double distance;
			try{
				port = Integer.parseInt(args[i]);
				distance = Double.parseDouble(args[i+1]);
				
				if(port < 1024 || distance < 0 || distance > 1){
					System.out.println("INvalid argument. Program terminates");
					System.exit(0);
				}
				
				tempTable.put(port, distance);
				
			}catch(Exception e){
				System.out.println("INvalid arguments. Program terminates");
				System.exit(0);
			}
		}
		
		if(args.length%2 == 0){
			if(args[args.length-1].equals("last")){
				isLast = true;
			}
			else{
				System.out.println("INvalid argument. Program terminates");
				System.exit(0);
			}
				
		}
		
		DatagramSocket clientSocket = new DatagramSocket(selfPort);
		
		if(isLast){
			String info = "";
			for(Integer i:tempTable.keySet()){
				info = info + "#" + String.valueOf(i) + "#" + String.valueOf(tempTable.get(i));
			}
			
			info = info.substring(1);
			sendRoutingTable("info", info, clientSocket);
		}
		
		while(true){
			
			
			
			byte[] receiveData = new byte[1024];
			DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
			
			try {
				clientSocket.receive(receivePacket);
			}
			catch (IOException e1) {
				e1.printStackTrace();	
			}
			
			String dataTemp = new String(receivePacket.getData());
			
			int length = receivePacket.getLength();
			String data = "";
		
			for(int i=0;i<length;i++)
			{
				data = data + dataTemp.charAt(i);
			}
			
			String[] msg = data.split("#");
			
			int senderPort = Integer.parseInt(msg[1]);
			
			System.out.println(currentTime()+"Message received at Node "+ selfPort +" from Node " + senderPort);
			
			HashMap<Integer, Double> receivedTable = new HashMap<Integer, Double>();
			
			for(int i = 3; i+1 < msg.length; i=i+2){
				receivedTable.put(Integer.parseInt(msg[i]), Double.parseDouble(msg[i+1]));
			}
			
			if(dv.isEmpty()){
				
				isUpdated = true;
				
				for(Integer i : tempTable.keySet()){
					dv.put(i, new table(i, tempTable.get(i)));
				}
			}
			
			neighbor.put(senderPort, receivedTable);
				
			for(Integer i:receivedTable.keySet()){
					
				if(i == selfPort){
					continue;
				}
					
				if(!dv.containsKey(i)){
					isUpdated = true;
					dv.put(i, new table(senderPort, tempTable.get(senderPort)+receivedTable.get(i)));
				}
					
				else if(dv.get(i).distance > tempTable.get(senderPort)+receivedTable.get(i)){
					
					isUpdated = true;
					double dis = tempTable.get(senderPort)+receivedTable.get(i);
					dv.put(i, new table(senderPort, dis));
				
				}
			}
			
			System.out.println(currentTime()+"Node "+selfPort+" Routing Table");		
			
			for(Integer i:dv.keySet()){
				if(i == dv.get(i).nextHop){
					System.out.println("- ("+(double)Math.round(dv.get(i).distance*100)/100+") -> Node "+i);
				}
				else{
					System.out.println("- ("+(double)Math.round(dv.get(i).distance*100)/100+") -> Node "+i+"; Next hop -> Node "+ dv.get(i).nextHop);
				}
				
			}
			
			if(isUpdated){
				String table = "";
				for(Integer i:dv.keySet()){
					table = table + "#" + String.valueOf(i) + "#" + String.valueOf(dv.get(i).distance);
				}
				table = table.substring(1);
				
				sendRoutingTable("table", table, clientSocket);
			}
		
		}
}
	
	
	private static void sendRoutingTable(String str, String s, DatagramSocket socket) throws UnknownHostException {
		
		s = String.valueOf(System.currentTimeMillis())+ "#" + String.valueOf(selfPort) + "#"+str+"#"+s; 
		byte[] sendData = s.getBytes();
		
		for(Integer i:tempTable.keySet()){
			try {
				DatagramPacket SendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("localHost"), i);
				socket.send(SendPacket);
				System.out.println(currentTime()+"Message sent from Node "+selfPort+" to Node "+i);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		isUpdated = false;
		
	}
	
	public static String currentTime() {
        String s = String.valueOf(System.currentTimeMillis());
        return "["+s.substring(0, 10)+"."+s.substring(10)+"]";
}

	public static int selfPort;
    public static HashMap<Integer, Double> tempTable = new HashMap<Integer, Double>();//this map contain a node's distance to neighbors.
	public static boolean isUpdated = false;
	public static boolean isLast = false;
	public static HashMap<Integer, table> dv = new HashMap<Integer, table>();
	public static HashMap<Integer, HashMap<Integer, Double>> neighbor = new HashMap<Integer, HashMap<Integer, Double>>();
}